/**
 * 
 */
package edu.unipd.p2t.connection;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.HashSet;

import android.util.Log;
import backport.android.bluetooth.BluetoothDevice;
import backport.android.bluetooth.BluetoothSocket;
import edu.unipd.p2t.db.Message;

/**
 * @author Tommaso Dal Sasso
 *
 */
public class BtDevice extends Thread implements Device {

	private static final String TAG = "p2t";

	/**
	 * The device we want connect to
	 */
	private final BluetoothDevice btDevice;

	/**
	 * The socket opened with the 
	 */
	private BluetoothSocket btSocket;
	
	private InputStream inStream;
	private OutputStream outStream;
	public static final int BUFFER_SIZE = 1024;
	
	private ObjectInputStream objInStream;
	private ObjectOutputStream objOutStream;
	
	
	/**
	 * Keeps track of corrently active bluetooth connections
	 */
	private static int activeConnections = 0;
	
	/**
	 * Says if the device is the first to transmit
	 */
	private boolean isFirst = false;

	/**
	 * Creates a new Device from an existing Bluetooth connection
	 * @param socket the existing connection
	 */
	public BtDevice( BluetoothDevice device, BluetoothSocket socket ) {
		Log.d( TAG, "BtDevice: constructordevice " + device.getName() + " created" );
		btDevice = device;
		btSocket = socket;
	}


	/**
	 * Creates a new Device from the given BluetoothDevice with a null socket
	 * @param btDevice the BluetoothDevice that describes the device to map
	 */
	public BtDevice( BluetoothDevice device ) {
		Log.d( TAG, "BtDevice: device " + device.getName() + " created" );
		btDevice = device;
	}
	
	
	@Override
	public void send( Object thing ) throws IOException {
		objOutStream.writeObject( thing );
	}

	
	@Override
	public Object receive() throws IOException {
		try {
			return objInStream.readObject();
			
		} catch( ClassNotFoundException cnfe ) {
			return null;
		}
	}

	
	/**
	 * Closes the connection with the device
	 */
	@Override
	public void close() {
		Log.d( TAG, "closing connection with " + btDevice.getName() );
		activeConnections--;
		
		try {
			btSocket.close();
		} catch (IOException e) {
			Log.e( TAG, "unable to close socket");
		}
	}


	/**
	 * Attempts to connect with a device with peer2tweet (must have the same UUID)
	 * @return true if the connection succeeds and we are awaiting to start communication, false otherwise
	 */
	@Override
	public boolean connect() {
		try {
			btSocket = btDevice.createRfcommSocketToServiceRecord( ConnectionManager.P2T_UUID );
		} catch (IOException e) {
			Log.e(TAG, "unable to open socket, connection error. Closing connection" );
			return false;
		}
		// OK, we've got a socket. Now let's open a connection
		start();
		return true;
	}


	@Override
	public void run() {
		// we stop discovery (too much expensive)
//		TODO: uncomment - ConnectionManager.getInstance().stopDiscovery();
	
		activeConnections++;
		try{
			// trying to connect with the device trough the socket
			Log.d( TAG, "Trying to connect through opened socket" );
			btSocket.connect();
			
			// OK, we are connected, initializing streams or die
			inStream  = btSocket.getInputStream();
			outStream = btSocket.getOutputStream();
			
			objInStream = new ObjectInputStream( inStream );
			objOutStream = new ObjectOutputStream( outStream );

		} catch( IOException eOpen ) {
			// unable to connect
			Log.e( TAG, "unable to connect through opened socket" );
			try {
				btSocket.close();
			} catch ( IOException eClose ) {
				Log.e( TAG, "ouch! What a mess, unable to close socket, no good..." );
			}
			// no communication opened, going back home...
			close();
			return;
		}
		
		// initializing buffer
		byte[] buffer = new byte[ BUFFER_SIZE ];
		int bytes;
		
		// main listen loop
		while( true ) {
			try {
				
				bytes = inStream.read( buffer );
				// TODO: do stuff with such bytes
				Log.d( TAG, "received: " + bytes );
				
			} catch( IOException e ) { // at connection interruption
				Log.d( TAG, "exiting main listen loop" );
				break;
			}
			
		}
		
		// closing connection
		close();
	}

	public void write( byte[] bytes ) {
		try {
			outStream.write( bytes );
		} catch( IOException e ) {
			Log.e( TAG, "write: unable to send message" );
		}
	}
	

	@Override
	public boolean isFirst() {
		return isFirst;
	}


	@Override
	public void setFirst( boolean isFirst ) {
		this.isFirst = isFirst;
	}


	@Override
	public HashSet<String> receiveIdList() throws IOException  {
		Object raw = receive();
		if( raw instanceof HashSet )
			return ( HashSet<String> ) raw;
		else
			return null;
	}


	@Override
	public Message[] receiveMessages() throws IOException  {
		return ( Message[] ) receive();
	}


	@Override
	public void sendHashList(HashSet<String> hl) throws IOException {
		send( hl );		
	}


	@Override
	public void sendMessages(Message[] msgs) throws IOException {
		send( msgs );
	}


	public static int getActiveConnections() {
		return activeConnections;
	}
	
	
	@Override
	public synchronized boolean equals( Object dev ) {
		
//		if( dev == null )
//			Log.d( TAG, "BtDevice.equals: invocation over null" );
//		else
//			Log.d( TAG, "BtDevice.equals: invocation over " + dev.getClass() );
		
		if( this == dev )
			return true;
		
		// this is a workaround to avoid NullPointerException that seems to be
		// caused by race condition in Thread registration
		if( btDevice == null ) {
			Log.e( TAG, "BtDevice.equals: my btDevice is null" );
			return super.equals( dev );
		}
		
		if( dev instanceof BtDevice ) {
			BtDevice remoteDevice = ( BtDevice ) dev;
			return btDevice.getAddress().equals( remoteDevice.btDevice.getAddress() );
		}
		
		return false;
	}
	
}
